package org.infodavid.common.messaging.impl;

import java.util.HashMap;
import java.util.Map;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.Topic;
import javax.naming.Context;
import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.infodavid.common.messaging.exceptions.MessagingException;
import org.infodavid.common.naming.NamingUtil;

/**
 * The Class ConnectionEntry.
 */
class ConnectionEntry implements ExceptionListener {

  /** The Constant LOG. */
  private static final Log LOG = LogFactory.getLog(ConnectionEntry.class);

  /** The associated address. */
  private final String address;

  /** The connection as QueueConnection. */
  private Connection connection;

  /** The destinations. */
  private final Map<String,Destination> destinations = new HashMap<String,Destination>();

  /**
   * Check if connection is closed.
   * @return true if connection == null
   */
  public boolean isClosed() {
    return connection == null;
  }

  /**
   * Constructor.
   * @param pAddress the address
   */
  protected ConnectionEntry(final String pAddress) {
    LOG.debug("Building ConnectionEntry");

    if (StringUtils.isEmpty(pAddress)) {
      address = null; // read from file: jndi.properties
    }
    else {
      address = NamingUtil.validateAddress(pAddress);
    }
  }

  /**
   * Close.
   */
  public synchronized void close() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Begin close() for address " + getAddress());
    }

    LOG.debug("Removing queues associated to the connection");

    synchronized (destinations) {
      destinations.clear();
    }

    assert destinations.size() == 0;

    if (connection != null) {
      if (address == null) {
        LOG.debug("Stopping the messaging connection on host defined in jndi.properties");
      }
      else {
        LOG.debug("Stopping the messaging connection on host: " + getAddress());
      }

      try {
        connection.stop();
      }
      catch (final Exception e) {
        LOG.error("An error occurs when closing JMS connection", e);
      }

      if (address == null) {
        LOG.debug("Closing the messaging connection on host defined in jndi.properties");
      }
      else {
        LOG.debug("Closing the messaging connection on host: " + getAddress());
      }

      try {
        connection.close();
      }
      catch (final Exception e) {
        LOG.error("An error occurs when closing JMS connection", e);
      }

      connection = null;
    }

    LOG.debug("End close()");
  }

  /**
   * Gets the address.
   * @return the address
   */
  public String getAddress() {
    return address;
  }

  /**
   * Method used to get or build a new connection.
   * @return the connection or null if an error occurs
   * @throws MessagingException the exception
   */
  public synchronized Connection getConnection() throws MessagingException {
    LOG.debug("Begin getConnection()");

    // validate the existing connection
    if (connection != null) {
      try {
        final String provider = connection.getMetaData().getJMSProviderName();

        if (LOG.isDebugEnabled()) {
          LOG.debug("JMS provider is " + provider);
        }
      }
      catch (final Exception e) {
        LOG.error("Error during check of JMS connection, closing it", e);

        close();
      }
    }

    // retrieve a connection if existing one was dirty or not previously built
    if (connection == null) {
      // Initialize JNDI
      LOG.debug("Instanciate IntializeContext.");

      Context ctx = null;

      try {
        ctx = NamingUtil.getContext(getAddress());

        if (LOG.isDebugEnabled()) {
          LOG.debug("Lookup " + NamingUtil.CONNECTION_FACTORY_JNDI_NAME);
        }

        final ConnectionFactory factory =
            (ConnectionFactory)ctx.lookup(NamingUtil.CONNECTION_FACTORY_JNDI_NAME);

        if (LOG.isDebugEnabled()) {
          LOG.debug("Creating JMS connection using Client ID: " + NamingUtil.HOSTNAME);
        }

        connection = factory.createConnection();
        connection.setClientID(NamingUtil.HOSTNAME);
        connection.setExceptionListener(this);
        connection.start();

        if (getAddress() == null) {
          LOG.debug("Messaging connection initialized for remote queues on host defined in jndi properties");
        }
        else {
          LOG.debug("Messaging connection initialized for remote queues on host: " + getAddress());
        }
      }
      catch (final Throwable e) {
        LOG.error("Error while getting connection: " + getAddress(), e);

        close();

        throw new MessagingException(e);
      }
      finally {
        if (ctx != null) {
          try {
            ctx.close();
          }
          catch (final Exception e) {
            LOG.error("Error closing the context", e);
          }
        }
      }
    }
    else {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Messaging connection retrieved from the cache");
      }
    }

    LOG.debug("End getConnection()");

    return connection;
  }

  /**
   * Method used to get or build a new destination.
   * @param name the name
   * @return the destination or null if an error occurs
   * @throws NamingException the exception
   */
  public synchronized Destination getDestination(final String name) throws NamingException {
    LOG.debug("Retrieving destination for " + name);

    Destination destination;

    destination = destinations.get(name);

    if (destination == null) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Lookup destination: " + name);
      }

      Context ctx = null;

      try {
        ctx = NamingUtil.getContext(getAddress());

        synchronized (destinations) {
          destination = (Destination)ctx.lookup(name);
          destinations.put(name, destination);
        }
      }
      finally {
        if (ctx != null) {
          try {
            ctx.close();
          }
          catch (final Exception e) {
            LOG.error("Error closing the context", e);
          }
        }
      }
    }
    else {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Destination retrieved from the cache");
      }
    }

    return destination;
  }

  /*
   * (non-Javadoc)
   * @see javax.jms.ExceptionListener#onException(javax.jms.JMSException)
   */
  public void onException(final JMSException exception) {
    LOG.error("An error occurs when processing JMS connection", exception);

    close();
  }

  /**
   * The description of the object.
   * @return the description of the object.
   */
  @Override
  public String toString() {
    final StringBuffer buffer = new StringBuffer();

    buffer.append(super.toString());
    buffer.append('[');
    buffer.append(" address = ").append(address);
    buffer.append(" connection = ").append(connection);
    buffer.append(" destinations = ");

    if (destinations.size() == 0) {
      buffer.append("[]");
    }
    else {
      try {
        for (final Destination destination : destinations.values()) {
          buffer.append('\t');

          if (destination instanceof Queue) {
            buffer.append(((Queue)destination).getQueueName());
          }
          else if (destination instanceof Topic) {
            buffer.append(((Topic)destination).getTopicName());
          }

          buffer.append('\n');
        }
      }
      catch (final Exception e) {
        LOG.debug("An error occurs while listing queues", e);
      }
    }

    buffer.append(']');

    return buffer.toString();
  }

  /**
   * Free resources.
   * @throws Throwable the throwable
   */
  @Override
  protected void finalize() throws Throwable {
    if (connection != null) {
      try {
        close();
      }
      catch (final Throwable e) {
        LOG.warn("An error occurs when closing JMS connection", e);
      }
    }

    super.finalize();
  }
}
